package com.cube.share.idempotence.annotations.aspects;

import com.cube.share.base.constants.Constant;
import com.cube.share.base.templates.ApiResult;
import com.cube.share.base.templates.CustomException;
import com.cube.share.base.utils.IpUtil;
import com.cube.share.idempotence.annotations.SupportIdempotence;
import com.cube.share.idempotence.util.RequestUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Objects;

/**
 * @author litb
 * @date 2021/3/11 14:22
 * @description 幂等性切面
 * eval "return redis.call('get',KEYS[1])" 1 key1
 */
@Component
@Slf4j
@Aspect
public class IdempotenceAspect {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 确保get和del两部操作原子性的lua脚本
     */
    private static final String LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    private static final String LUA_IGNORE_VALUE_SCRIPT = "if redis.call('get',KEYS[1]) ~= nil then return redis.call('del',KEYS[1]) else return 0 end";

    private static final RedisScript<Long> REDIS_GET_DEL_SCRIPT = new DefaultRedisScript<>(LUA_SCRIPT, Long.class);

    private static final RedisScript<Long> REDIS_GET_DEL_IGNORE_VALUE_SCRIPT = new DefaultRedisScript<>(LUA_IGNORE_VALUE_SCRIPT, Long.class);

    @Pointcut("@annotation(com.cube.share.idempotence.annotations.SupportIdempotence)")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object before(ProceedingJoinPoint joinPoint) throws Throwable {
        //获取注解属性
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        SupportIdempotence idempotence = method.getAnnotation(SupportIdempotence.class);
        String keyPrefix = idempotence.prefix();
        boolean alert = idempotence.alert();
        boolean ignoreValue = idempotence.ignoreValue();

        //从方法中获取请求头
        HttpServletRequest request = Objects.requireNonNull(((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())).getRequest();
        String ip = IpUtil.getIpAddress(request);
        //前端传入的token
        String token = RequestUtil.getRequestAttribute(Constant.IDEMPOTENCE_TOKEN_HEADER);

        //token校验
        if (token == null) {
            log.error("请求头缺少幂等性token,url_path = {}", request.getServletPath());
            throw new CustomException("幂等性校验token缺失");
        }
        //拼接成存放在redis中的key
        String redisKey = keyPrefix + token;
        //执行lua脚本
        Long result;
        if (ignoreValue) {
            result = stringRedisTemplate.execute(REDIS_GET_DEL_IGNORE_VALUE_SCRIPT, Collections.singletonList(redisKey));
        } else {
            result = stringRedisTemplate.execute(REDIS_GET_DEL_SCRIPT, Collections.singletonList(redisKey), ip);
        }
        if (result == null || result == 0L) {
            if (alert) {
                throw new CustomException("请勿重复提交");
            } else {
                return ApiResult.success();
            }
        }
        return joinPoint.proceed();
    }
}
